package pl.edu.pb.wi.pwnography.controller;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import pl.edu.pb.wi.pwnography.model.ParsedData;
import pl.edu.pb.wi.pwnography.model.form.ChartLineForm;
import pl.edu.pb.wi.pwnography.model.form.ChartScatterForm;
import pl.edu.pb.wi.pwnography.modules.KNNAllAttributes;
import pl.edu.pb.wi.pwnography.modules.KNearestNeighbor;
import pl.edu.pb.wi.pwnography.session.ChartLine;
import pl.edu.pb.wi.pwnography.session.ChartScatter;
import pl.edu.pb.wi.pwnography.session.FileList;

@Controller
@RequestMapping("/module/chart")
public class ChartModule {
    private static final Logger log = Logger.getLogger(ChartModule.class
	    .getName());

    private static final String SCATTER_SUCCESS = "Zmiany zostały prawidłowo zapisane. Po prawej stronie powinien być widoczny wygenerowany wykres.";
    private static final String SCATTER_ERROR = "Wystąpił problem. Sprawdź ponownie wybrane kolumny.";
    private static final String SCATTER_NO_DATA = "BRAK DANYCH";

    private FileList fileList;
    private ChartScatter chartScatter;
    private ChartLine chartLine;

    @Autowired
    public ChartModule(FileList fileList, ChartScatter chartScatter,
	    ChartLine chartLine) {
	super();
	this.fileList = fileList;
	this.chartScatter = chartScatter;
	this.chartLine = chartLine;
    }

    public void setFileList(FileList fileList) {
	this.fileList = fileList;
    }

    public void setChartScatter(ChartScatter chartScatter) {
	this.chartScatter = chartScatter;
    }

    @ModelAttribute("floatColumnList")
    public List<String> getFloatColumnNames() {
	return fileList.getActiveFile().getFloatTypeColumnNames();
    }

    @ModelAttribute("stringColumnList")
    public List<String> getStringColumnNames() {
	return fileList.getActiveFile().getColumnNames();
    }

    @RequestMapping(value = "scatter", method = RequestMethod.GET)
    public String scatter(Model model) {
	log.info("Chart scatter GET.");

	model.addAttribute("chart", new ChartScatterForm());

	return "module/chart/scatter";
    }

    @RequestMapping(value = "scatter", method = RequestMethod.POST)
    public String scatter(
	    @ModelAttribute("chart") @Valid ChartScatterForm chartForm,
	    BindingResult results, Model model) {
	log.info("Chart scatter POST.");

	if (results.hasErrors()) {
	    return "module/chart/scatter";
	}

	if (getFloatColumnNames().contains(chartForm.getxColumn())
		&& getFloatColumnNames().contains(chartForm.getyColumn())
		&& getStringColumnNames().contains(chartForm.getClassColumn())) {
	    chartScatter.setScatterForm(chartForm);

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { SCATTER_SUCCESS }));
	} else {
	    log.info(String.format(
		    "Problem with selected column: X [%s] Y [%s] Class [%s]",
		    chartForm.getxColumn(), chartForm.getyColumn(),
		    chartForm.getClassColumn()));

	    model.addAttribute("errorMsgs",
		    Arrays.asList(new String[] { SCATTER_ERROR }));
	}

	return "module/chart/scatter";
    }

    @RequestMapping("scatter.json")
    public @ResponseBody
    ResponseEntity<String> scatter(HttpServletResponse response) {
	HttpHeaders responseHeaders = new HttpHeaders();
	responseHeaders.add("Content-Type", "application/json; charset=utf-8");

	ParsedData activeFile = fileList.getActiveFile();
	String classColumn = chartScatter.getScatterForm().getClassColumn();
	String xColumn = chartScatter.getScatterForm().getxColumn();
	String yColumn = chartScatter.getScatterForm().getyColumn();

	boolean returnEmpty = false;
	if (activeFile == null || classColumn == null || xColumn == null
		|| yColumn == null
		|| !activeFile.getColumnNames().contains(classColumn)
		|| !activeFile.getFloatTypeColumnNames().contains(xColumn)
		|| !activeFile.getFloatTypeColumnNames().contains(yColumn))
	    returnEmpty = true;

	String mainTitle = String.format("'%s' kontra '%s' dla klas '%s'",
		xColumn, yColumn, classColumn);
	String titleJson = String.format("\"mainTitle\" : \"%s\"",
		returnEmpty ? SCATTER_NO_DATA : mainTitle);

	String xAxisJson = String.format("\"xAxisTitle\" : \"%s\"",
		returnEmpty ? SCATTER_NO_DATA : xColumn);

	String yAxisJson = String.format("\"yAxisTitle\" : \"%s\"",
		returnEmpty ? SCATTER_NO_DATA : yColumn);

	StringBuilder series = new StringBuilder("[");
	Random rand = new Random();
	int seriesCount = 0;
	int dataCount = 0;
	if (!returnEmpty) {
	    for (String rowClass : activeFile
		    .getDistinctColumnValues(classColumn)) {
		if (seriesCount > 0)
		    series = series.append(", ");
		series = series.append("{ \"name\": \"").append(rowClass)
			.append("\",");
		series = series
			.append(String.format(
				"\"color\": \"rgba(%d, %d, %d, .5)\",",
				rand.nextInt(255), rand.nextInt(255),
				rand.nextInt(255)));
		series = series.append("\"data\": [");

		dataCount = 0;
		for (int i = 0; i < activeFile.getRowCount(); ++i) {
		    if (!activeFile.getCell(classColumn, i).toString()
			    .equals(rowClass.toString()))
			continue;
		    else {
			if (dataCount > 0)
			    series = series.append(", ");
			series = series.append("[")
				.append(activeFile.getCell(xColumn, i))
				.append(", ")
				.append(activeFile.getCell(yColumn, i))
				.append("]");
			++dataCount;
		    }
		}
		series = series.append("]}");
		++seriesCount;
	    }
	}
	series = series.append("]");

	String seriesJson = String.format("\"series\" : %s", series);
	String resultJson = new StringBuilder("{").append(titleJson)
		.append(",").append(xAxisJson).append(",").append(yAxisJson)
		.append(",").append(seriesJson).append("}").toString();

	return new ResponseEntity<String>(resultJson, responseHeaders,
		HttpStatus.CREATED);
    }

    @RequestMapping(value = "line", method = RequestMethod.GET)
    public String line(Model model) {
	log.info("Chart line GET.");

	model.addAttribute("chart", new ChartLineForm());

	return "module/chart/line";
    }

    @RequestMapping(value = "line", method = RequestMethod.POST)
    public String line(@ModelAttribute("chart") @Valid ChartLineForm form,
	    BindingResult results, Model model) {
	log.info("Chart scatter POST.");

	if (results.hasErrors()) {
	    return "module/chart/line";
	}

	try {
	    int maxNeignbors = fileList.getActiveFile().getRowCount();
	    Map<String, List<Object>> data = fileList.getActiveFile().getData();
	    ParsedData activeFile = fileList.getActiveFile();

	    Random rand = new Random();
	    int seriesCount = 0;
	    int dataCount = 0;

	    StringBuilder series = new StringBuilder("[");
	    List<Object> newClasses;
	    Float assessmentQuality;
	    for (KNearestNeighbor.METRIC metric : KNearestNeighbor.METRIC
		    .values()) {
		if (seriesCount > 0)
		    series = series.append(", ");
		series = series.append("{ \"name\": \"").append(metric)
			.append("\",");
		series = series
			.append(String.format(
				"\"color\": \"rgba(%d, %d, %d, .5)\",",
				rand.nextInt(255), rand.nextInt(255),
				rand.nextInt(255)));
		series = series.append("\"data\": [");

		dataCount = 0;

		for (int i = 1; i < maxNeignbors; ++i) {
		    if (dataCount > 0)
			series = series.append(", ");

		    log.info(String.format("-------------------\nBefore leave-one-out %d.", i));

		    newClasses = KNNAllAttributes.leaveOneOut(activeFile,
			    form.getExcludedColumns(), form.getColumnName(), i,
			    metric, maxNeignbors, data);

		    log.info(String.format("After leave-one-out %d.", i));

		    assessmentQuality = KNNAllAttributes.assessmentQuality(
			    activeFile.getColumnValues(form.getColumnName()),
			    newClasses)
			    * Float.valueOf(100);

		    log.info(String.format("After assessment quality %d.", i));

		    series = series.append("[").append(Integer.toString(i))
			    .append(", ").append(assessmentQuality.toString())
			    .append("]");
		    ++dataCount;

		    log.info(String.format("Finnished iteration %d.", i));
		}

		series = series.append("]}");
		++seriesCount;

		// log.info(String.format("Finnished metric %s.", metric));
	    }
	    series = series.append("]");

	    chartLine.setSeries(series.toString());

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { SCATTER_SUCCESS }));
	} catch (Exception e) {
	    log.info(String.format("Problem with line chart. Exception: %s",
		    e.getMessage()));

	    model.addAttribute("errorMsgs", Arrays.asList(new String[] {
		    SCATTER_ERROR, e.getMessage() }));
	}

	return "module/chart/line";
    }

    @RequestMapping("line.json")
    public @ResponseBody
    ResponseEntity<String> line(HttpServletResponse response) {
	HttpHeaders responseHeaders = new HttpHeaders();
	responseHeaders.add("Content-Type", "application/json; charset=utf-8");

	return new ResponseEntity<String>(chartLine.getSeries(),
		responseHeaders, HttpStatus.CREATED);
    }

}
